style: blog redesign v2 — premium full-page editorial layout#7
style: blog redesign v2 — premium full-page editorial layout#7hallelx2 wants to merge 2 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds ChangesVercel Monorepo Deployment Configuration
Blog Page Refactor
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Each app deploys as its own Vercel project off this repo, scoped by Root Directory: vercel.json pins buildCommand (turbo run build --filter) + ignoreCommand (turbo-ignore) so projects only rebuild on relevant changes. DEPLOY.md documents the per-project root dirs, env, domains, and remote cache. turbo build verified green across web/blog/docs.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
DEPLOY.md (2)
168-168: 💤 Low valueMinor: simplify redundant phrasing.
Line 168 can be shortened from "outside of the Root Directory" to "outside the Root Directory" for cleaner documentation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@DEPLOY.md` at line 168, In the DEPLOY.md file, locate the phrase "Include source files outside of the Root Directory" in the documentation about the Root Directory setting. Remove the word "of" from the phrase "outside of the Root Directory" so it reads "outside the Root Directory" instead, which provides cleaner and more concise documentation phrasing.Source: Linters/SAST tools
182-183: ⚡ Quick winClarify the CLI workflow instruction.
Line 182 uses
! vercel loginwith a comment "(interactive browser)". The!prefix is non-standard; this appears to mean "runvercel logininteractively in your browser." Consider rephrasing to avoid ambiguity, such as:vercel login # opens interactive browser login.Additionally, the instruction lists only
apps/blogsin the loop but says "repeat for web, docs"—adding a clear example for all three apps would improve clarity:# from repo root: vercel login cd apps/blogs && vercel link && vercel --prod cd ../docs && vercel link && vercel --prod cd ../web && vercel link && vercel --prod🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@DEPLOY.md` around lines 182 - 183, The DEPLOY.md file contains ambiguous CLI workflow instructions that need clarification. First, replace the non-standard `! vercel login` syntax on line 182 with clearer instruction format using `vercel login # opens interactive browser login` to make it obvious this opens an interactive browser. Second, expand the instruction on line 183 which currently only shows the `apps/blogs` example but references "repeat for web, docs"—replace this with explicit examples showing all three applications (blogs, docs, and web) with the complete sequence of commands for each directory (cd into apps/blogs, run vercel link and vercel --prod, then repeat for the other two apps) to make the deployment process unambiguous.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/blogs/app/page.tsx`:
- Around line 375-380: The openSandbox function and handleRetrieve function (and
other related async handlers at lines 389-429, 1008-1009, 1106-1107) have
unmanaged async state updates that continue even after the modal closes. Use an
AbortController or a mounted ref to track the modal state, and pass this abort
signal to all async operations in handleRetrieve and related functions. Before
applying state updates like setRetrievedResult in the async callbacks, check if
the abort signal was triggered or if the modal is still open, and skip the
updates if the modal has been closed to prevent stale data from overwriting
current state.
- Around line 524-529: The mobile navigation links in the map function iterating
over ['How it works', 'Docs', 'Whitepaper', 'Pricing'] have href="#" which
prevents navigation to the actual intended destinations. Create a navigation
route mapping object that maps each item name to its corresponding URL path,
then replace the hardcoded href="#" with a lookup into this mapping using the
item variable to ensure mobile nav links navigate to the same destinations as
the desktop navigation.
- Around line 593-598: Replace all three avatar `<img>` tags that render
activeArticle.author.avatarUrl with Next.js Image component. For the first
instance around line 593-598 (showing activeArticle.author.name as alt), add
sizes="44px" prop and import Image from next/image. Repeat this replacement for
the other two avatar renders at lines 801-806 with sizes="36px" and lines
882-887 with sizes="28px", each displaying the same author avatar. Additionally,
update apps/blogs/next.config.ts to add images.unsplash.com to the
remotePatterns configuration so that unsplash image URLs are allowed (currently
only picsum.photos is configured).
---
Nitpick comments:
In `@DEPLOY.md`:
- Line 168: In the DEPLOY.md file, locate the phrase "Include source files
outside of the Root Directory" in the documentation about the Root Directory
setting. Remove the word "of" from the phrase "outside of the Root Directory" so
it reads "outside the Root Directory" instead, which provides cleaner and more
concise documentation phrasing.
- Around line 182-183: The DEPLOY.md file contains ambiguous CLI workflow
instructions that need clarification. First, replace the non-standard `! vercel
login` syntax on line 182 with clearer instruction format using `vercel login #
opens interactive browser login` to make it obvious this opens an interactive
browser. Second, expand the instruction on line 183 which currently only shows
the `apps/blogs` example but references "repeat for web, docs"—replace this with
explicit examples showing all three applications (blogs, docs, and web) with the
complete sequence of commands for each directory (cd into apps/blogs, run vercel
link and vercel --prod, then repeat for the other two apps) to make the
deployment process unambiguous.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4c779ce4-40cd-4e2f-b251-5616b1633266
📒 Files selected for processing (5)
DEPLOY.mdapps/blogs/app/page.tsxapps/blogs/vercel.jsonapps/docs/vercel.jsonapps/web/vercel.json
| const openSandbox = useCallback(() => { | ||
| setIngestionStep(0); | ||
| setRetrievedResult(null); | ||
| setHighlightedSection(null); | ||
| setShowDemoModal(true); | ||
| }; | ||
| }, []); |
There was a problem hiding this comment.
Prevent stale retrieval updates after modal close/reopen.
handleRetrieve schedules async state updates that are never canceled. If the modal is closed (or reopened) before timeout completion, stale results can still land and overwrite current modal state.
Suggested fix
+import React, { useState, useEffect, useCallback, useRef } from 'react';
export default function BlogPage() {
+ const retrieveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const openSandbox = useCallback(() => {
+ if (retrieveTimerRef.current) clearTimeout(retrieveTimerRef.current);
setIngestionStep(0);
+ setIsRetrieving(false);
+ setDemoQuery('');
setRetrievedResult(null);
setHighlightedSection(null);
setShowDemoModal(true);
}, []);
const handleRetrieve = useCallback((e: React.FormEvent) => {
e.preventDefault();
if (!demoQuery.trim()) return;
+ if (retrieveTimerRef.current) clearTimeout(retrieveTimerRef.current);
setIsRetrieving(true);
setRetrievedResult(null);
setHighlightedSection(null);
- setTimeout(() => {
+ retrieveTimerRef.current = setTimeout(() => {
setIsRetrieving(false);
// existing branching...
- }, 1200);
+ retrieveTimerRef.current = null;
+ }, 1200);
}, [demoQuery, selectedDoc]);
+ useEffect(() => {
+ return () => {
+ if (retrieveTimerRef.current) clearTimeout(retrieveTimerRef.current);
+ };
+ }, []);
- onClick={() => setShowDemoModal(false)}
+ onClick={() => {
+ if (retrieveTimerRef.current) clearTimeout(retrieveTimerRef.current);
+ setIsRetrieving(false);
+ setShowDemoModal(false);
+ }}Also applies to: 389-429, 1008-1009, 1106-1107
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/blogs/app/page.tsx` around lines 375 - 380, The openSandbox function and
handleRetrieve function (and other related async handlers at lines 389-429,
1008-1009, 1106-1107) have unmanaged async state updates that continue even
after the modal closes. Use an AbortController or a mounted ref to track the
modal state, and pass this abort signal to all async operations in
handleRetrieve and related functions. Before applying state updates like
setRetrievedResult in the async callbacks, check if the abort signal was
triggered or if the modal is still open, and skip the updates if the modal has
been closed to prevent stale data from overwriting current state.
| {['How it works', 'Docs', 'Whitepaper', 'Pricing'].map((item) => ( | ||
| <Link | ||
| key={item} | ||
| href="#" | ||
| onClick={() => setIsNavOpen(false)} | ||
| className="text-[15px] font-medium text-[#0A0A0A] p-2 rounded-lg hover:bg-black/5" |
There was a problem hiding this comment.
Mobile nav links are non-functional (href="#").
The mobile menu currently drops users on the same page instead of navigating to the intended destinations, unlike desktop nav.
Suggested fix
- {['How it works', 'Docs', 'Whitepaper', 'Pricing'].map((item) => (
+ {[
+ { label: 'How it works', href: 'https://vectorless.store/#how' },
+ { label: 'Docs', href: 'https://docs.vectorless.store' },
+ { label: 'Whitepaper', href: 'https://vectorless.store/whitepaper' },
+ { label: 'Pricing', href: 'https://vectorless.store/#pricing' },
+ ].map((item) => (
<Link
- key={item}
- href="#"
+ key={item.label}
+ href={item.href}
onClick={() => setIsNavOpen(false)}
className="text-[15px] font-medium text-[`#0A0A0A`] p-2 rounded-lg hover:bg-black/5"
>
- {item}
+ {item.label}
</Link>
))}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {['How it works', 'Docs', 'Whitepaper', 'Pricing'].map((item) => ( | |
| <Link | |
| key={item} | |
| href="#" | |
| onClick={() => setIsNavOpen(false)} | |
| className="text-[15px] font-medium text-[#0A0A0A] p-2 rounded-lg hover:bg-black/5" | |
| {[ | |
| { label: 'How it works', href: 'https://vectorless.store/#how' }, | |
| { label: 'Docs', href: 'https://docs.vectorless.store' }, | |
| { label: 'Whitepaper', href: 'https://vectorless.store/whitepaper' }, | |
| { label: 'Pricing', href: 'https://vectorless.store/#pricing' }, | |
| ].map((item) => ( | |
| <Link | |
| key={item.label} | |
| href={item.href} | |
| onClick={() => setIsNavOpen(false)} | |
| className="text-[15px] font-medium text-[`#0A0A0A`] p-2 rounded-lg hover:bg-black/5" | |
| > | |
| {item.label} | |
| </Link> | |
| ))} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/blogs/app/page.tsx` around lines 524 - 529, The mobile navigation links
in the map function iterating over ['How it works', 'Docs', 'Whitepaper',
'Pricing'] have href="#" which prevents navigation to the actual intended
destinations. Create a navigation route mapping object that maps each item name
to its corresponding URL path, then replace the hardcoded href="#" with a lookup
into this mapping using the item variable to ensure mobile nav links navigate to
the same destinations as the desktop navigation.
| <img | ||
| src={activeArticle.author.avatarUrl} | ||
| alt={activeArticle.author.name} | ||
| className="object-cover w-full h-full" | ||
| referrerPolicy="no-referrer" | ||
| /> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify where <img> is used in this page and whether remote image domains are configured for next/image.
rg -n '<img|avatarUrl|images.unsplash.com|remotePatterns|images:' apps/blogs/app/page.tsx next.config.* apps/**/next.config.* 2>/dev/nullRepository: hallelx2/vectorless
Length of output: 1173
🏁 Script executed:
# Check the remotePatterns configuration in next.config.ts
cat -n apps/blogs/next.config.ts | head -20Repository: hallelx2/vectorless
Length of output: 651
🏁 Script executed:
# Check if next/image Image component is already imported in page.tsx
rg -n "^import.*from.*next/image|^import.*Image from" apps/blogs/app/page.tsxRepository: hallelx2/vectorless
Length of output: 45
🏁 Script executed:
# Check the exact line numbers and context for the three img tags
sed -n '593,598p; 801,806p; 882,887p' apps/blogs/app/page.tsx | cat -nRepository: hallelx2/vectorless
Length of output: 1108
🏁 Script executed:
# Check the full remotePatterns configuration
cat apps/blogs/next.config.tsRepository: hallelx2/vectorless
Length of output: 986
🏁 Script executed:
# Check if there are any eslint rules configured for next/image or unoptimized images
rg -n "next/image|unoptimized|next-image" apps/blogs --type json --type ts --type tsxRepository: hallelx2/vectorless
Length of output: 91
🏁 Script executed:
# Check for eslint configuration related to images
rg -n "next/image|unoptimized" apps/blogs --type json --type js --type tsRepository: hallelx2/vectorless
Length of output: 140
🏁 Script executed:
# Check if .eslintrc or similar config exists
fd -e eslintrc -e eslintrc.json -e eslintrc.js apps/blogsRepository: hallelx2/vectorless
Length of output: 409
🏁 Script executed:
# Find eslint config files in apps/blogs
fd -e eslintrc -e eslintrc.json -e eslintrc.js . apps/blogsRepository: hallelx2/vectorless
Length of output: 45
🏁 Script executed:
# Check if there's a root eslint config
fd -e eslintrc -e eslintrc.json -e eslintrc.js . --max-depth 1Repository: hallelx2/vectorless
Length of output: 45
🏁 Script executed:
# Also check the web app's next.config to see if it has the same remotePatterns
cat apps/web/next.config.ts | grep -A 20 "images:"Repository: hallelx2/vectorless
Length of output: 767
🏁 Script executed:
# Check the actual avatarUrl values used in the data
sed -n '31,62p' apps/blogs/app/page.tsx | cat -nRepository: hallelx2/vectorless
Length of output: 1217
🏁 Script executed:
# Search for any references to unsplash in next config
rg -n "unsplash" apps/blogsRepository: hallelx2/vectorless
Length of output: 495
🏁 Script executed:
# Check for any CI/lint configuration files that might catch unoptimized images
fd -e yml -e yaml -e json . --max-depth 2 | grep -E "(lint|eslint|ci|github|build)" | head -20Repository: hallelx2/vectorless
Length of output: 45
🏁 Script executed:
# Check if there's a biome or other formatter config
fd -e biome.json -e prettier.config . --max-depth 2Repository: hallelx2/vectorless
Length of output: 45
🏁 Script executed:
# Look at package.json scripts to see what lint commands exist
rg -n "lint|eslint" apps/blogs/package.jsonRepository: hallelx2/vectorless
Length of output: 155
🏁 Script executed:
# Check the root eslintrc or any eslint config
fd -e eslintrc . --max-depth 3Repository: hallelx2/vectorless
Length of output: 45
🏁 Script executed:
# Check if eslint-config-next is being used in any eslintrc
cat apps/blogs/.eslintrc* 2>/dev/null || cat .eslintrc* 2>/dev/null || echo "No eslintrc found"Repository: hallelx2/vectorless
Length of output: 87
🏁 Script executed:
# Check package.json for eslint-plugin-next or `@next/eslint-plugin-next`
rg -n "`@next/eslint-plugin`|eslint-plugin-next|eslint-config-next" apps/blogs/package.jsonRepository: hallelx2/vectorless
Length of output: 102
🏁 Script executed:
# Check the context around the img tags to see parent container dimensions
sed -n '585,605p' apps/blogs/app/page.tsx | cat -nRepository: hallelx2/vectorless
Length of output: 1257
🏁 Script executed:
# Check the second location
sed -n '790,815p' apps/blogs/app/page.tsx | cat -nRepository: hallelx2/vectorless
Length of output: 1660
🏁 Script executed:
# Check the third location
sed -n '870,895p' apps/blogs/app/page.tsx | cat -nRepository: hallelx2/vectorless
Length of output: 1650
Update next/image implementation and add unsplash domain to remotePatterns.
This applies to all three avatar renders. Note that migrating to next/image requires updating apps/blogs/next.config.ts to add images.unsplash.com to remotePatterns, as the current configuration only allows picsum.photos.
For each location, use the corresponding container size:
- Line 593-598 (44×44px):
sizes="44px" - Line 801-806 (36×36px):
sizes="36px" - Line 882-887 (28×28px):
sizes="28px"
Configuration and code changes required
apps/blogs/next.config.ts - Add unsplash domain:
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'picsum.photos',
port: '',
pathname: '/**',
},
+ {
+ protocol: 'https',
+ hostname: 'images.unsplash.com',
+ port: '',
+ pathname: '/**',
+ },
],
},apps/blogs/app/page.tsx - Replace <img> with <Image>:
+import Image from 'next/image';
-<img
- src={activeArticle.author.avatarUrl}
- alt={activeArticle.author.name}
- className="object-cover w-full h-full"
- referrerPolicy="no-referrer"
-/>
+<Image
+ src={activeArticle.author.avatarUrl}
+ alt={activeArticle.author.name}
+ fill
+ sizes="44px"
+ className="object-cover"
+ referrerPolicy="no-referrer"
+/>(Repeat for lines 801-806 with sizes="36px" and 882-887 with sizes="28px")
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <img | |
| src={activeArticle.author.avatarUrl} | |
| alt={activeArticle.author.name} | |
| className="object-cover w-full h-full" | |
| referrerPolicy="no-referrer" | |
| /> | |
| import Image from 'next/image'; | |
| <Image | |
| src={activeArticle.author.avatarUrl} | |
| alt={activeArticle.author.name} | |
| fill | |
| sizes="44px" | |
| className="object-cover" | |
| referrerPolicy="no-referrer" | |
| /> |
🧰 Tools
🪛 GitHub Check: Lint & Typecheck
[warning] 593-593:
Using <img> could result in slower LCP and higher bandwidth. Consider using <Image /> from next/image or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/blogs/app/page.tsx` around lines 593 - 598, Replace all three avatar
`<img>` tags that render activeArticle.author.avatarUrl with Next.js Image
component. For the first instance around line 593-598 (showing
activeArticle.author.name as alt), add sizes="44px" prop and import Image from
next/image. Repeat this replacement for the other two avatar renders at lines
801-806 with sizes="36px" and lines 882-887 with sizes="28px", each displaying
the same author avatar. Additionally, update apps/blogs/next.config.ts to add
images.unsplash.com to the remotePatterns configuration so that unsplash image
URLs are allowed (currently only picsum.photos is configured).
Source: Linters/SAST tools
Summary
Complete redesign of the Vectorless Blog (�pps/blogs) with a premium, full-page editorial layout that matches the web landing page's visual language.
Changes
Design tokens (matched from web)
Closes HAL-333
Summary by CodeRabbit
New Features
Documentation
Chores